/*
 * Copyright (C) 2015   Jeremy Chen jeremy_cz@yahoo.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <common_base/CBaseSession.h>
#include <utils/Log.h>
#include <utils/CFdbIfMessageHeader.h>
#include <common_base/CSocketImp.h>
#include <common_base/CFdbSessionContainer.h>
#include <common_base/CBaseEndpoint.h>

CBaseSession::CBaseSession(CFdbSessionContainer *container, CSocketImp *socket)
    : CBaseFdWatch(socket->getFd(), POLLIN | POLLHUP | POLLERR)
    , mContainer(container)
    , mSocket(socket)
    , mPayloadBuffer(0)
{
    if (mContainer->owner()->enableAysncRead())
    {
        mInputChunk.checkBufferSize();
    }
}

void CBaseSession::onInputReady()
{
    while (1)
    {
        if (fatalError())
        {
            if (mPayloadBuffer)
            {
                delete[] mPayloadBuffer;
                mPayloadBuffer = 0;
            }
            break;
        }
        if (mPayloadBuffer)
        {
            if (mInputChunk.mConsumed < (int32_t)mMsgPrefix.mTotalLength)
            {
                break;
            }
            memcpy(mPayloadBuffer, mInputChunk.mBuffer + mInputChunk.mOffset,
                   mMsgPrefix.mTotalLength);
            mInputChunk.mOffset += mMsgPrefix.mTotalLength;
            mInputChunk.mConsumed -= mMsgPrefix.mTotalLength;
            processPayload();
        }
        else
        {
            if (mInputChunk.mConsumed < CFdbMessage::mPrefixSize)
            {
                break;
            }
            parsePrefix(mInputChunk.mBuffer + mInputChunk.mOffset);
        }
    }
}

void CBaseSession::onInput()
{
    if (fatalError())
    {
        return;
    }
    uint8_t prefix_buffer[CFdbMessage::mPrefixSize];
    if (receiveData(prefix_buffer, sizeof(prefix_buffer)))
    {
        parsePrefix(prefix_buffer);
    }
    if (fatalError())
    {
        if (mPayloadBuffer)
        {
            delete[] mPayloadBuffer;
            mPayloadBuffer = 0;
        }
        return;
    }
    if (receiveData(mPayloadBuffer + CFdbMessage::mPrefixSize,
                     mMsgPrefix.mTotalLength - CFdbMessage::mPrefixSize))
    {
        processPayload();
    }

    if (mPayloadBuffer)
    {
        delete[] mPayloadBuffer;
        mPayloadBuffer = 0;
    }
}

void CBaseSession::parsePrefix(const uint8_t *data)
{
    mMsgPrefix.deserialize(data);
    int32_t data_size = mMsgPrefix.mTotalLength - CFdbMessage::mPrefixSize;
    if (data_size < 0)
    {
        fatalError(true);
        return;
    }
    /*
     * The leading CFdbMessage::mPrefixSize bytes are not used; just for
     * keeping uniform structure
     */
    try
    {
        mPayloadBuffer = new uint8_t[mMsgPrefix.mTotalLength];
    }
    catch (...)
    {
        LOG_E("CBaseSession: Session %d: Unable to allocate buffer of size %d!\n",
                sid(), mMsgPrefix.mTotalLength);
        fatalError(true);
    }
}

void CBaseSession::processPayload()
{
    const uint8_t *data = mPayloadBuffer + CFdbMessage::mPrefixSize;
    int32_t size = mMsgPrefix.mTotalLength - CFdbMessage::mPrefixSize;
    if (size < 0)
    {
        fatalError(true);
        if (mPayloadBuffer)
        {
            delete[] mPayloadBuffer;
            mPayloadBuffer = 0;
        }
        return;
    }

    NFdbBase::CFdbMessageHeader head;
    CFdbParcelableParser parser(head);
    if (!parser.parse(data, mMsgPrefix.mHeadLength))
    {
        LOG_E("CBaseSession: Session %d: Unable to deserialize message head!\n", sid());
        fatalError(true);
        if (mPayloadBuffer)
        {
            delete[] mPayloadBuffer;
            mPayloadBuffer = 0;
        }
        return;
    }

    auto type = head.type();
    switch (type)
    {
        case FDB_MT_REQUEST:
        case FDB_MT_SIDEBAND_REQUEST:
        case FDB_MT_GET_EVENT:
        case FDB_MT_PUBLISH:
            doStatistics(type, head.flag(), mStatistics.mRx);
            doRequest(head);
            break;
        case FDB_MT_RETURN_EVENT:
        case FDB_MT_REPLY:
        case FDB_MT_SIDEBAND_REPLY:
        case FDB_MT_STATUS:
            doResponse(head);
            break;
        case FDB_MT_BROADCAST:
            doStatistics(type, head.flag(), mStatistics.mRx);
            doBroadcast(head);
            break;
        case FDB_MT_SUBSCRIBE_REQ:
            if (head.code() == FDB_CODE_SUBSCRIBE)
            {
                doSubscribeReq(head, true);
            }
            else if (head.code() == FDB_CODE_UNSUBSCRIBE)
            {
                doSubscribeReq(head, false);
            }
            else if (head.code() == FDB_CODE_UPDATE)
            {
                doUpdate(head);
            }
            break;
        default:
            LOG_E("CBaseSession: Message %d: Unknown type!\n", (int32_t)head.serial_number());
            fatalError(true);
            break;
    }

    if (mPayloadBuffer)
    {
        delete[] mPayloadBuffer;
        mPayloadBuffer = 0;
    }
}

int32_t CBaseSession::readStream(uint8_t *data, int32_t size)
{
    return mSocket->recv(data, size);
}

bool CBaseSession::isSecure() const
{
    return mContainer->isSecure();
}

void CBaseSession::doStatistics(EFdbMessageType type, uint32_t flag, CStatisticsData &stat_data)
{
    switch (type)
    {
        case FDB_MT_REQUEST:
            if (flag & MSG_FLAG_NOREPLY_EXPECTED)
            {
                stat_data.mSend++;
            }
            else
            {
                if (flag & MSG_FLAG_SYNC_REPLY)
                {
                    stat_data.mSyncRequest++;
                }
                else
                {
                    stat_data.mAsyncRequest++;
                }
            }
        break;
        case FDB_MT_STATUS:
            if (flag & MSG_FLAG_SYNC_REPLY)
            {
                stat_data.mSyncStatus++;
            }
            else
            {
                stat_data.mAsyncStatus++;
            }
        break;
        case FDB_MT_GET_EVENT:
            stat_data.mGetEvent++;
        break;
        case FDB_MT_REPLY:
            if (flag & MSG_FLAG_SYNC_REPLY)
            {
                stat_data.mSyncReply++;
            }
            else
            {
                stat_data.mAsyncReply++;
            }
        break;
        case FDB_MT_RETURN_EVENT:
            stat_data.mReturnEvent++;
        break;
        case FDB_MT_BROADCAST:
            stat_data.mBroadcast++;
        break;
        case FDB_MT_PUBLISH:
            stat_data.mPublish++;
        break;
        default:
        break;
    }
}

